Skip to content

Control Sandbox SMTP server based on DynamoDB config, save config on app register from Saleor data#2292

Draft
witoszekdev wants to merge 9 commits intomainfrom
eng-1402-add-support-for-smtp-app-additional-register-data
Draft

Control Sandbox SMTP server based on DynamoDB config, save config on app register from Saleor data#2292
witoszekdev wants to merge 9 commits intomainfrom
eng-1402-add-support-for-smtp-app-additional-register-data

Conversation

@witoszekdev
Copy link
Copy Markdown
Contributor

@witoszekdev witoszekdev commented Mar 27, 2026

Adds configuration in DynamoDB for controlling the Sandbox SMTP server.

Copilot AI review requested due to automatic review settings March 27, 2026 15:20
@witoszekdev witoszekdev requested a review from a team as a code owner March 27, 2026 15:20
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 27, 2026

🦋 Changeset detected

Latest commit: 77848c0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
saleor-app-smtp Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
saleor-app-smtp Error Error Comment Mar 27, 2026 3:21pm
8 Skipped Deployments
Project Deployment Actions Updated (UTC)
saleor-app-avatax Ignored Ignored Mar 27, 2026 3:21pm
saleor-app-cms Ignored Ignored Mar 27, 2026 3:21pm
saleor-app-klaviyo Ignored Ignored Mar 27, 2026 3:21pm
saleor-app-payment-np-atobarai Ignored Ignored Mar 27, 2026 3:21pm
saleor-app-payment-stripe Ignored Ignored Mar 27, 2026 3:21pm
saleor-app-products-feed Ignored Ignored Mar 27, 2026 3:21pm
saleor-app-search Ignored Ignored Mar 27, 2026 3:21pm
saleor-app-segment Ignored Ignored Mar 27, 2026 3:21pm

Request Review

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 27, 2026

Codecov Report

❌ Patch coverage is 77.74295% with 71 lines in your changes missing coverage. Please review.
✅ Project coverage is 37.40%. Comparing base (8b9365d) to head (77848c0).

Files with missing lines Patch % Lines
apps/smtp/src/pages/api/register.ts 0.00% 28 Missing ⚠️
...es/app-configuration/ui/configuration-fallback.tsx 0.00% 24 Missing ⚠️
...es/smtp/configuration/smtp-configuration.router.ts 10.00% 18 Missing ⚠️
apps/smtp/src/pages/configuration/index.tsx 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2292      +/-   ##
==========================================
+ Coverage   37.21%   37.40%   +0.19%     
==========================================
  Files        1015     1019       +4     
  Lines       65896    66131     +235     
  Branches     3384     3429      +45     
==========================================
+ Hits        24522    24738     +216     
- Misses      40999    41018      +19     
  Partials      375      375              
Flag Coverage Δ
avatax 57.39% <ø> (ø)
cms 18.67% <ø> (ø)
domain 100.00% <ø> (ø)
dynamo-config-repository 79.29% <ø> (ø)
errors 91.66% <ø> (ø)
logger 28.81% <ø> (ø)
np-atobarai 72.54% <ø> (ø)
products-feed 5.91% <ø> (ø)
search 30.74% <ø> (ø)
segment 32.38% <ø> (ø)
shared 37.35% <ø> (ø)
smtp 36.64% <77.74%> (+1.77%) ⬆️
stripe 71.09% <ø> (ø)
webhook-utils 11.02% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown
Contributor

Differences Found

✅ No packages or licenses were added.

Summary

Expand
License Name Package Count Packages
0BSD 1
Packages
  • tslib
CC BY-SA 4.0 1
Packages
  • @cspell/dict-en-common-misspellings
CC0-1.0 1
Packages
  • type-fest
MIT (http://mootools.net/license.txt) 1
Packages
  • slick
MIT/X11 1
Packages
  • nub
Public Domain 1
Packages
  • jsonify
Python-2.0 1
Packages
  • argparse
SEE LICENSE IN LICENSE 1
Packages
  • spawndamnit
SEE LICENSE IN LICENSE.md 1
Packages
  • lightcookie
Unlicense 1
Packages
  • @sinonjs/text-encoding
WTFPL 1
Packages
  • opener
BlueOak-1.0.0 3
Packages
  • jackspeak
  • package-json-from-dist
  • path-scurry
CC-BY-4.0 3
Packages
  • @saleor/macaw-ui
  • caniuse-lite
  • saleor-apps
LGPL-3.0-or-later 11
Packages
  • @img/sharp-libvips-darwin-arm64
  • @img/sharp-libvips-darwin-x64
  • @img/sharp-libvips-linux-arm
  • @img/sharp-libvips-linux-arm64
  • @img/sharp-libvips-linux-s390x
  • @img/sharp-libvips-linux-x64
  • @img/sharp-libvips-linuxmusl-arm64
  • @img/sharp-libvips-linuxmusl-x64
  • @img/sharp-wasm32
  • @img/sharp-win32-ia32
  • @img/sharp-win32-x64
BSD-2-Clause 22
Packages
  • cheerio-select
  • css-select
  • css-what
  • domelementtype
  • domhandler
  • domutils
  • dotenv
  • entities
  • escodegen
  • eslint-scope
  • espree
  • esprima
  • esrecurse
  • estraverse
  • esutils
  • glob-to-regexp
  • nth-check
  • shimmer
  • terser
  • uglify-js
  • And 2 more...
<<missing>> 26
Packages
  • @saleor/app-problems
  • @saleor/apps-domain
  • @saleor/apps-logger
  • @saleor/apps-otel
  • @saleor/apps-shared
  • @saleor/apps-trpc
  • @saleor/apps-ui
  • @saleor/dynamo-config-repository
  • @saleor/errors
  • @saleor/eslint-config-apps
  • @saleor/react-hook-form-macaw
  • @saleor/sentry-utils
  • @saleor/typescript-config-apps
  • @saleor/webhook-utils
  • busboy
  • json-query
  • saleor-app-avatax
  • saleor-app-cms
  • saleor-app-klaviyo
  • saleor-app-payment-np-atobarai
  • And 6 more...
BSD-3-Clause 48
Packages
  • @protobufjs/aspromise
  • @protobufjs/base64
  • @protobufjs/codegen
  • @protobufjs/eventemitter
  • @protobufjs/fetch
  • @protobufjs/float
  • @protobufjs/inquire
  • @protobufjs/path
  • @protobufjs/pool
  • @protobufjs/utf8
  • @saleor/app-sdk
  • @saleor/eslint-plugin-saleor-app
  • @sentry/cli
  • @sentry/cli-darwin
  • @sentry/cli-linux-arm
  • @sentry/cli-linux-arm64
  • @sentry/cli-linux-i686
  • @sentry/cli-linux-x64
  • @sentry/cli-win32-i686
  • @sentry/cli-win32-x64
  • And 28 more...
ISC 55
Packages
  • @bundled-es-modules/cookie
  • @bundled-es-modules/statuses
  • @bundled-es-modules/tough-cookie
  • @isaacs/cliui
  • abbrev
  • anymatch
  • boolbase
  • cli-width
  • cliui
  • concat-with-sourcemaps
  • electron-to-chromium
  • fastq
  • flatted
  • foreground-child
  • form-data-lite
  • fs.realpath
  • get-caller-file
  • glob
  • glob-parent
  • graceful-fs
  • And 35 more...
Apache-2.0 237
Packages
  • @ampproject/remapping
  • @aws-crypto/crc32
  • @aws-crypto/crc32c
  • @aws-crypto/ie11-detection
  • @aws-crypto/sha1-browser
  • @aws-crypto/sha256-browser
  • @aws-crypto/sha256-js
  • @aws-crypto/supports-web-crypto
  • @aws-crypto/util
  • @aws-sdk/abort-controller
  • @aws-sdk/chunked-blob-reader
  • @aws-sdk/client-dynamodb
  • @aws-sdk/client-s3
  • @aws-sdk/client-sso
  • @aws-sdk/client-sso-oidc
  • @aws-sdk/client-sts
  • @aws-sdk/config-resolver
  • @aws-sdk/core
  • @aws-sdk/credential-provider-env
  • @aws-sdk/credential-provider-http
  • And 217 more...
MIT 1410
Packages
  • @0no-co/graphql.web
  • @adobe/css-tools
  • @algolia/cache-browser-local-storage
  • @algolia/cache-common
  • @algolia/cache-in-memory
  • @algolia/client-account
  • @algolia/client-analytics
  • @algolia/client-common
  • @algolia/client-personalization
  • @algolia/client-search
  • @algolia/logger-common
  • @algolia/logger-console
  • @algolia/recommend
  • @algolia/requester-browser-xhr
  • @algolia/requester-common
  • @algolia/requester-node-http
  • @algolia/transporter
  • @apidevtools/json-schema-ref-parser
  • @ardatan/relay-compiler
  • @ardatan/sync-fetch
  • And 1390 more...

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces DynamoDB-backed, per-tenant control over the SMTP fallback (“Sandbox SMTP”) behavior, including persisting fallback settings from Saleor /register additional_data, and using that config during email sending.

Changes:

  • Bump @saleor/app-sdk to 1.7.2-dev.1 and adjust pnpm trust policy exclusions.
  • Add DynamoDB-backed fallback SMTP config repository/service, persist config on app registration, and expose/manage it via tRPC + UI.
  • Update email sending use case to optionally redirect fallback emails to a configured address.

Reviewed changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
pnpm-workspace.yaml Updates app-sdk version and pnpm trust policy exclusions for the dev build.
pnpm-lock.yaml Lockfile update to reflect the app-sdk version bump and dependency graph changes.
apps/smtp/src/pages/configuration/index.tsx Surfaces fallbackRedirectEmail in the fallback section UI and triggers fallback settings mutation.
apps/smtp/src/pages/api/register.ts Saves fallback config on register (from additional_data) before creating webhooks.
apps/smtp/src/modules/trpc/protected-client-procedure-with-services.ts Injects fallback config service into tRPC context.
apps/smtp/src/modules/smtp/configuration/smtp-configuration.service.ts Removes fallback settings methods from metadata-based SMTP configuration service.
apps/smtp/src/modules/smtp/configuration/smtp-configuration.router.ts Switches fallback settings endpoints to DynamoDB-backed service and extends API with redirect email.
apps/smtp/src/modules/fallback-smtp/on-auth-apl-saved.ts New helper to parse and persist fallback config from register payload.
apps/smtp/src/modules/fallback-smtp/on-auth-apl-saved.test.ts Unit tests for saving fallback config on register.
apps/smtp/src/modules/fallback-smtp/fallback-smtp-service.ts New service that wires env gating + DynamoDB repository for fallback config.
apps/smtp/src/modules/fallback-smtp/fallback-smtp-service.test.ts Unit tests for fallback service behavior and caching.
apps/smtp/src/modules/fallback-smtp/fallback-smtp-config-repository.ts New DynamoDB repository for fallback config storage.
apps/smtp/src/modules/fallback-smtp/fallback-smtp-config-repository.test.ts Unit tests for DynamoDB repository behaviors and defaults.
apps/smtp/src/modules/fallback-smtp/fallback-register-data.ts New parser/validator for additional_data fallback config.
apps/smtp/src/modules/fallback-smtp/fallback-register-data.test.ts Unit tests for register additional_data parsing behavior.
apps/smtp/src/modules/event-handlers/use-case/send-event-messages.use-case.ts Uses DynamoDB-backed fallback config; supports redirecting recipient when configured.
apps/smtp/src/modules/event-handlers/use-case/send-event-messages.use-case.test.ts Updates tests to cover new fallback config source and redirect behavior.
apps/smtp/src/modules/event-handlers/use-case/send-event-messages.use-case.factory.ts Injects fallback config service into the use case.
apps/smtp/src/modules/app-configuration/ui/configuration-fallback.tsx Displays fallback redirect info when configured.
apps/smtp/scripts/setup-dynamodb.ts Adds local DynamoDB table setup script.
apps/smtp/package.json Adds setup-dynamodb script.
apps/smtp/README.md Documents fallback SMTP + DynamoDB usage and local setup steps.
apps/smtp/.env.example Adds DynamoDB-related env vars for fallback SMTP mode.
.devcontainer/smtp/docker-compose.yml Adds a local dynamodb-local service for development.
.changeset/metal-crews-chew.md Changeset entry describing the new fallback config behavior.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

"saleor-app-smtp": minor
---

App will now parse `additional_data` received from Saleor in /register endpoint and use it to save Sandbox SMTP configuration in DynamoDB. This configuration toggles if SMTP Sandbox server should be used and if all sent email receipient should be overwritten to a single email adderss.
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling fixes needed in the changeset text (e.g. "receipient" → "recipient", "adderss" → "address"). These typos will end up in the published changelog.

Suggested change
App will now parse `additional_data` received from Saleor in /register endpoint and use it to save Sandbox SMTP configuration in DynamoDB. This configuration toggles if SMTP Sandbox server should be used and if all sent email receipient should be overwritten to a single email adderss.
App will now parse `additional_data` received from Saleor in /register endpoint and use it to save Sandbox SMTP configuration in DynamoDB. This configuration toggles if SMTP Sandbox server should be used and if all sent email recipient should be overwritten to a single email address.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to 48
const fallbackConfigRepo = new FallbackSmtpService({
saleorApiUrl: ctx.saleorApiUrl,
});

const result = await next({
ctx: {
smtpConfigurationService,
featureFlagService,
fallbackConfigRepo,
},
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fallbackConfigRepo is an instance of FallbackSmtpService (a service) rather than a repository. Renaming this (and the corresponding ctx field) to fallbackConfigService (or similar) would make the dependency clearer and avoid confusion with the underlying FallbackSmtpConfigRepository.

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +107
* For existing installations before this check was introduced,
* we allow using SMTP fallback without redirect email restrictions
*/
if (!result.Item) {
return { fallbackEnabled: true, fallbackRedirectEmail: null };
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetchConfig() returns { fallbackEnabled: true, fallbackRedirectEmail: null } when the DynamoDB item is missing. This makes fallback SMTP enabled by default for any tenant without an explicit config record, which contradicts the goal of controlling fallback via DynamoDB and also differs from the previous default (useSaleorSmtpFallback: false). Consider returning an error / “not configured” state when Item is missing, or defaulting fallbackEnabled to false and handling legacy installs via an explicit migration path.

Suggested change
* For existing installations before this check was introduced,
* we allow using SMTP fallback without redirect email restrictions
*/
if (!result.Item) {
return { fallbackEnabled: true, fallbackRedirectEmail: null };
* When no configuration is stored in DynamoDB,
* fallback SMTP is disabled by default.
*/
if (!result.Item) {
return { fallbackEnabled: false, fallbackRedirectEmail: null };

Copilot uses AI. Check for mistakes.
Comment on lines +238 to 247
if (!fallbackSmtpConfig) {
this.logger.info("Fallback SMTP env vars are not configured");

return err([
new SendEventMessagesUseCase.InvalidEmailAddressError(
"Received an invalid email address: couldn't determine the domain",
new SendEventMessagesUseCase.FallbackNotConfiguredError(
"Fallback enabled but env vars are not configured",
{
props: { channelSlug, event, recipientEmail },
props: { channelSlug, event },
},
),
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When fallback SMTP env vars are missing, the returned error message says "Fallback enabled but env vars are not configured", but at this point the code hasn’t checked whether fallback is enabled in DynamoDB yet. This can be misleading in logs/UI. Consider changing the message to reflect the actual condition (e.g. "Fallback SMTP env vars are not configured").

Copilot uses AI. Check for mistakes.
Comment thread apps/smtp/README.md
This feature is controlled by:

1. **Fallback SMTP env vars** (`FALLBACK_SMTP_HOST`, etc.) - set by Saleor Cloud on the deployment, defining the actual SMTP server credentials
2. **Per-tenant config in DynamoDB** - stores `fallbackEnabled` (boolean) and optional `fallbackRedirectEmail` (string) per installation
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README says the DynamoDB config is stored “per installation”, but the implementation uses DynamoMainTable.getPrimaryKeyScopedToSaleorApiUrl({ saleorApiUrl }), which is tenant-scoped (persists across reinstalls). Either adjust the README wording or change the PK strategy to match the intended scoping.

Suggested change
2. **Per-tenant config in DynamoDB** - stores `fallbackEnabled` (boolean) and optional `fallbackRedirectEmail` (string) per installation
2. **Per-tenant config in DynamoDB** - stores `fallbackEnabled` (boolean) and optional `fallbackRedirectEmail` (string) per Saleor API URL (tenant), shared across app reinstalls

Copilot uses AI. Check for mistakes.
Comment thread apps/smtp/README.md
1. **Fallback SMTP env vars** (`FALLBACK_SMTP_HOST`, etc.) - set by Saleor Cloud on the deployment, defining the actual SMTP server credentials
2. **Per-tenant config in DynamoDB** - stores `fallbackEnabled` (boolean) and optional `fallbackRedirectEmail` (string) per installation

When a store installs the app, Saleor can pass `additional_data` with `fallbackEnabled` and `fallbackRedirectEmail`. The app validates and stores this in DynamoDB. If no `additional_data` is provided or `fallbackEnabled` is missing, fallback is not configured.
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README states: "If no additional_data is provided or fallbackEnabled is missing, fallback is not configured." Current implementation defaults to fallbackEnabled: true when the DynamoDB item is missing, and parseFallbackRegisterData returns a disabled config (and saves it) when fallbackEnabled is missing but additional_data is present. Consider updating the docs to match the actual behavior, or adjust the code to enforce the documented “not configured” state.

Suggested change
When a store installs the app, Saleor can pass `additional_data` with `fallbackEnabled` and `fallbackRedirectEmail`. The app validates and stores this in DynamoDB. If no `additional_data` is provided or `fallbackEnabled` is missing, fallback is not configured.
When a store installs the app, Saleor can pass `additional_data` with `fallbackEnabled` and `fallbackRedirectEmail`. The app validates and stores this in DynamoDB. If no `additional_data` is provided, the app assumes fallback is enabled (subject to the env vars being set). If `additional_data` is provided but `fallbackEnabled` is missing, the app stores a disabled fallback configuration by default.

Copilot uses AI. Check for mistakes.
Comment thread pnpm-workspace.yaml

trustPolicy: no-downgrade

# TODO: Remove one once released
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor wording issue in the TODO: "Remove one once released" is unclear/grammatically incorrect. Consider rephrasing to something like "Remove once released" or specify what should be removed.

Suggested change
# TODO: Remove one once released
# TODO: Remove this exclusion once the stable version is released

Copilot uses AI. Check for mistakes.
@@ -98,6 +98,7 @@ const ConfigurationPage: NextPage = () => {
fallbackSettingsMutation.mutate({ useSaleorSmtpFallback: newValue });
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateFallbackSmtpSettings mutation is called without fallbackRedirectEmail, but the router currently treats an omitted value as null (clears it). This means toggling the checkbox will unintentionally wipe an existing redirect email. Consider passing the currently loaded fallbackRedirectEmail along with useSaleorSmtpFallback, or changing the API to support partial updates without clearing fields when they are omitted.

Suggested change
fallbackSettingsMutation.mutate({ useSaleorSmtpFallback: newValue });
if (!fallbackSettingsQuery.data) {
return;
}
fallbackSettingsMutation.mutate({
useSaleorSmtpFallback: newValue,
fallbackRedirectEmail: fallbackSettingsQuery.data.fallbackRedirectEmail,
});

Copilot uses AI. Check for mistakes.
Comment on lines +409 to +418
return ctx.fallbackConfigRepo
.setFallbackConfig({
fallbackEnabled: input.useSaleorSmtpFallback,
fallbackRedirectEmail: input.fallbackRedirectEmail ?? null,
})
.match(
(v) => v,
() => ({
useSaleorSmtpFallback: input.useSaleorSmtpFallback,
fallbackRedirectEmail: input.fallbackRedirectEmail ?? null,
}),
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fallbackRedirectEmail is optional(), but the implementation writes fallbackRedirectEmail: input.fallbackRedirectEmail ?? null. When clients omit the field (e.g. current UI), this will clear any previously stored redirect email. If the intent is “leave unchanged when omitted”, handle undefined separately (read existing config first, or require the field in input).

Copilot uses AI. Check for mistakes.

const configResult = await ctx.fallbackConfigRepo.getFallbackConfig();

return { isConfigured: configResult.isOk() };
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isFallbackSmtpConfigured currently reports isConfigured: true whenever getFallbackConfig() returns Ok. Since the repository returns Ok even when the item doesn’t exist (default config), this endpoint will treat “no DynamoDB record” as “configured”. If “configured” is meant to indicate an explicit per-tenant decision from additional_data / DynamoDB, consider checking for item existence (or exposing a dedicated isConfigured method) rather than relying on isOk().

Suggested change
return { isConfigured: configResult.isOk() };
if (configResult.isErr()) {
// If the configuration cannot be loaded, treat it as not explicitly configured.
return { isConfigured: false };
}
const config = configResult.value;
const isConfigured =
config.fallbackEnabled === true ||
config.fallbackRedirectEmail !== null && config.fallbackRedirectEmail !== undefined;
return { isConfigured };

Copilot uses AI. Check for mistakes.
@wcislo-saleor
Copy link
Copy Markdown
Member

@witoszekdev, in the context of the work we discussed these changes are no longer necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants